package nl.liacs.mms.dn.server;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import nl.liacs.mms.dn.entities.DigitalNote;

import com.beoui.geocell.GeocellManager;
import com.beoui.geocell.model.BoundingBox;
import com.beoui.geocell.model.Point;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;

public class DigitalNotesService {

	DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

	private final String ENTITY_NAME = "DigitalNote";
	private String hostname;
	
	public void setHostname(String hostname){
		this.hostname = hostname;
	}

	//
	// Convert Services
	//
	protected DigitalNote RequestToDigitalNote(HttpServletRequest req) {
		DigitalNote dn = new DigitalNote();
		dn.setUserEmail(req.getParameter("email"));
		dn.setTitle(req.getParameter("title"));
		dn.setText(req.getParameter("text"));
		if (req.getParameter("longitude") != null && !req.getParameter("longitude").isEmpty())
			dn.setLongitude(Double.parseDouble(req.getParameter("longitude")));
		if (req.getParameter("latitude") != null && !req.getParameter("latitude").isEmpty())
			dn.setLatitude(Double.parseDouble(req.getParameter("latitude")));

		if (dn.getLatitude() != 0 && dn.getLongitude() != 0) {
			Point p = new Point(dn.getLatitude(), dn.getLongitude());
			List<String> cells = GeocellManager.generateGeoCell(p);
			dn.setCells(cells);
		}

		return dn;
	}
	
	protected List<DigitalNote> EntitiesToDigitalNotes(List<Entity> entities){
		List<DigitalNote> notes = new LinkedList<DigitalNote>();
		for(Entity entity:entities){
			notes.add(EntityToDigitalNote(entity));
		}
		return notes;
	}
	
	protected DigitalNote EntityToDigitalNote(Entity entity){
		DigitalNote dn = new DigitalNote();
		dn.setKey(entity.getKey());
		if(entity.getProperty("email") !=null)
			dn.setUserEmail(entity.getProperty("email").toString());
		dn.setTitle(entity.getProperty("title").toString());
		dn.setText(entity.getProperty("text").toString());
		if (entity.getProperty("longitude") != null)
			dn.setLongitude(Double.parseDouble(entity.getProperty("longitude").toString()));
		if (entity.getProperty("latitude") != null)
			dn.setLatitude(Double.parseDouble(entity.getProperty("latitude").toString()));

		if(entity.getProperty("imageKey") != null)
			dn.setImageKey(new BlobKey(entity.getProperty("imageKey").toString()));
		/*
		if (dn.getLatitude() != 0 && dn.getLongitude() != 0) {
			Point p = new Point(dn.getLatitude(), dn.getLongitude());
			List<String> cells = GeocellManager.generateGeoCell(p);
			dn.setCells(cells);
		}*/
		return dn;
	}
	
	protected void addDistancesAndCardinalDirections(List<DigitalNote> notes,double lng,double lat){
		for(DigitalNote note:notes){
			this.addDistanceAndCardinalDirection(note,lng,lat);
		}
	}
	
	protected void addDistanceAndCardinalDirection(DigitalNote note,double lng,double lat){
		double distance = this.getDistanceBetweenLocations(note.getLongitude(),note.getLatitude(),lng,lat);
		String cardinalDir = this.getCardinalDirection(note.getLongitude(),note.getLatitude(),lng,lat);
		note.setDistance(distance);
		note.setCardinalDirection(cardinalDir);
		
	}

	private Entity DigitalNoteToEntity(DigitalNote dn) {
		Entity note = new Entity(ENTITY_NAME);
		note.setProperty("email", dn.getUserEmail());
		note.setProperty("title", dn.getTitle());
		note.setProperty("text", dn.getText());
		note.setProperty("latitude", dn.getLatitude());
		note.setProperty("longitude", dn.getLongitude());
		note.setProperty("cells", dn.getCells());
		if(dn.getImageKey() != null)
			note.setProperty("imageKey", dn.getImageKey().getKeyString());
		return note;
	}

	//
	// Convert XML Services
	//
	protected String EntitiesToXML(List<Entity> notes) {
		StringBuffer sf = new StringBuffer();
		sf.append("<notes>");
		for (Entity note : notes) {
			sf.append(EntityToXML(note));
		}
		sf.append("</notes>");
		return sf.toString();

	}

	protected String EntityToXML(Entity note) {
		StringBuffer sf = new StringBuffer();
		sf.append("<note>");
		sf.append("<title>" + note.getProperty("title") + "</title>");
		sf.append("<text>" + note.getProperty("text") + "</text>");
		sf.append("<latitude>" + note.getProperty("latitude") + "</latitude>");
		sf.append("<longitude>" + note.getProperty("longitude") + "</longitude>");
		sf.append("</note>");
		return sf.toString();
	}
	
	protected String DigitalNotesToXML(List<DigitalNote> notes) {
		StringBuffer sf = new StringBuffer();
		sf.append("<notes>");
		for (DigitalNote note : notes) {
			sf.append(DigitalNoteToXML(note));
		}
		sf.append("</notes>");
		return sf.toString();

	}
	
	protected String DigitalNoteToXML(DigitalNote note) {
		StringBuffer sf = new StringBuffer();
		sf.append("<note>");
		sf.append("<id>" + note.getKey().getId() + "</id>");
		sf.append("<title>" + note.getTitle() + "</title>");
		sf.append("<text>" + note.getText() + "</text>");
		sf.append("<latitude>" + note.getLatitude() + "</latitude>");
		sf.append("<longitude>" + note.getLongitude() + "</longitude>");
		if(note.getDistance() != null)
			sf.append("<distance>" + note.getDistance() + "</distance>");
		if(note.getCardinalDirection() != null)
			sf.append("<cardinalDirection>" + note.getCardinalDirection() + "</cardinalDirection>");
		//if(note.getImageKey() != null)
		//	sf.append("<imageUrl>" + hostname + "/image?key=" + note.getImageKey().getKeyString() + "</imageUrl>");
		sf.append("</note>");
		return sf.toString();
	}

	//
	// Datastore Services
	//
	protected void saveDigitalNote(DigitalNote dn) {
		Entity note = this.DigitalNoteToEntity(dn);
		// set created date to current date
		Date created = new Date();
		note.setProperty("created", created);
		datastore.put(note);
	}

	protected void updateDigitalNote(DigitalNote dn) {
		Entity note = this.DigitalNoteToEntity(dn);
		// set updated date to current date
		Date updated = new Date();
		note.setProperty("updated", updated);
		datastore.put(note);
	}

	public List<DigitalNote> getAllNotes() {
		Query photoQuery = new Query(ENTITY_NAME);
		List<Entity> entities = datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());
		return EntitiesToDigitalNotes(entities);
	}
	
	public List<DigitalNote> getAllNotesFromUser(String email) {
		Filter filter = new FilterPredicate("email",FilterOperator.EQUAL,email);
		Query query = new Query(ENTITY_NAME).setFilter(filter);
		List<Entity> entities = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults());
		return EntitiesToDigitalNotes(entities);
	}
	
	public void deleteNote(String email,String id){
		Key key = KeyFactory.createKey(ENTITY_NAME,Long.parseLong(id));
		datastore.delete(key);
	}

	protected List<DigitalNote> getNotesNearby(double lng, double lat) {
		double range = 1.0;

		double latS = lat - range;
		double latN = lat + range;

		double lonW = lng - range;
		double lonE = lng + range;

		// Transform this to a bounding box
		BoundingBox bb = new BoundingBox(latN, lonE, latS, lonW);
		List<String> cells = GeocellManager.bestBboxSearchCells(bb, null);

		Filter filter = new FilterPredicate("cells", FilterOperator.IN, cells);
		Query query = new Query(ENTITY_NAME).setFilter(filter);
		List<Entity> entities = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults());
		return EntitiesToDigitalNotes(entities);

	}
	
	//
	// GEO Calculations
	//
	/**
	 * 
	 * @param lng1 longitude of origin point
	 * @param lat1 latitude of origin point
	 * @param lng2 longitude of second point
	 * @param lat2 longitude of second point
	 * @return distance between points in meters
	 */
	protected double getDistanceBetweenLocations(double lng1,double lat1,double lng2,double lat2){
		  double R = 6371000; // earth radius in meter
		  double dLat = Math.toRadians(lat2-lat1);
		  double dLng = Math.toRadians(lng2-lng1);
		  lat1 = Math.toRadians(lat1);
		  lat2 = Math.toRadians(lat2);
		  
		  double a = Math.pow(Math.sin(dLat/2),2) + Math.pow(Math.sin(dLng/2),2) * Math.cos(lat1) * Math.cos(lat2);
		  double c = 2 * Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
		  return R * c;
	}
	
	//private String[] cardinals = {"N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"};
	private String[] cardinals = {"N","NE","E","SE","S","SW","W","NW"};
	/**
	 * 
	 * @param lng1 longitude of origin point
	 * @param lat1 latitude of origin point
	 * @param lng2 longitude of second point
	 * @param lat2 longitude of second point
	 * @return cardinal direction from origin point to second point
	 */
	protected String getCardinalDirection(double lng1,double lat1,double lng2,double lat2){
		double x = (lng2-lng1) * Math.cos((lat1+lat2)/2);
	    double y = (lat2-lat1);
		double angle = (Math.toDegrees(Math.atan2(y,x))+360)%360;
		double splits = 360/cardinals.length;
		int index = (int)(Math.floor((float)(angle/splits)));
		return cardinals[index];
	}

}
